package x.ovo.wechat.bot.impl.plugin;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.net.URL;
import java.net.URLClassLoader;

/**
 * 自定义的插件类加载器，继承自URLClassLoader，用于加载插件类，并实现黑名单机制。
 *
 * @author ovo on 2024/07/09.
 */
@Slf4j
public class PluginClassLoader extends URLClassLoader {

    /** 系统类加载器作为父类加载器 */
    private static final ClassLoader SYSTEM_CLASS_LOADER = ClassLoader.getSystemClassLoader();
    /** 类加载器的黑名单，列出不使用自定义类加载器加载的包名 */
    private static final String[] BLACK_LIST = {
            "org.slf4j",
            "x.ovo.wechat.bot.core",
            "picocli.CommandLine"
    };

    public PluginClassLoader(URL[] urls) {
        super(urls, SYSTEM_CLASS_LOADER);
    }

    /**
     * 重写loadClass方法，实现类的加载逻辑。
     *
     * @param name    类的全限定名
     * @param resolve 是否解析类
     * @return 加载的类
     * @throws ClassNotFoundException 如果类未找到
     */
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // 判断是否在黑名单中，若在则尝试从父类加载器或系统类加载器加载
        Class<?> value = this.judgeBlackList(name);
        if (null == value) value = this.innerLoadClass(name); // 不在黑名单中，尝试从当前类加载器加载

        if (resolve) resolveClass(value); // 解析类
        return value;
    }

    /**
     * 判断类是否在黑名单中，若在则尝试从父类加载器或系统类加载器加载该类。
     *
     * @param name 类的全限定名
     * @return 加载的类，若未加载则返回null
     * @throws ClassNotFoundException 如果类未找到
     */
    @SneakyThrows
    private Class<?> judgeBlackList(String name) {
        for (String black : BLACK_LIST) { // 遍历黑名单
            if (name.startsWith(black)) { // 如果类名以黑名单中的包名开始
                if (super.getParent() != null) return super.getParent().loadClass(name); // 尝试从父类加载器加载
                else return super.findSystemClass(name); // 否则，尝试从系统类加载器加载
            }
        }
        return null; // 类不在黑名单中，返回null
    }

    /**
     * 尝试从当前类加载器加载类，如果未加载，则尝试从父类加载器或系统类加载器加载。
     *
     * @param name 类的全限定名
     * @return 加载的类
     * @throws ClassNotFoundException 如果类未找到
     */
    @SneakyThrows
    private Class<?> innerLoadClass(String name) {
        // 尝试从当前类加载器已加载的类中查找
        Class<?> loadedClass = super.findLoadedClass(name);
        if (null != loadedClass)
            log.debug("[{}] 在已加载的类中存在，类加载器：{}", name, loadedClass.getClassLoader().getName());

        // 如果类未被加载，则尝试进行加载
        if (null == loadedClass) {
            try {
                // 尝试通过当前类加载器加载类
                loadedClass = super.findClass(name);
                if (null == loadedClass) {
                    // 如果父类加载器存在，则尝试使用父类加载器加载类；否则，使用系统类加载器加载类
                    if (super.getParent() != null) loadedClass = super.getParent().loadClass(name);
                    else loadedClass = super.findSystemClass(name);
                }
            } catch (Exception e) {
                if (super.getParent() != null) loadedClass = super.getParent().loadClass(name);
                else loadedClass = super.findSystemClass(name);
            }
        }
        return loadedClass; // 返回加载的类
    }
}
